/*
 * Copyright 2012 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#ifndef SkPathOpsCurve_DEFINE
#define SkPathOpsCurve_DEFINE

#include "include/core/SkPath.h"
#include "include/core/SkPoint.h"
#include "include/core/SkScalar.h"
#include "include/core/SkTypes.h"
#include "include/private/base/SkDebug.h"
#include "src/pathops/SkIntersections.h"
#include "src/pathops/SkPathOpsConic.h"
#include "src/pathops/SkPathOpsCubic.h"
#include "src/pathops/SkPathOpsLine.h"
#include "src/pathops/SkPathOpsPoint.h"
#include "src/pathops/SkPathOpsQuad.h"
#include "src/pathops/SkPathOpsTypes.h"

struct SkPathOpsBounds;

struct SkOpCurve {
    SkPoint fPts[4];
    SkScalar fWeight;
    SkDEBUGCODE(SkPath::Verb fVerb);

    const SkPoint &operator[](int n) const
    {
        SkASSERT(n >= 0 && n <= SkPathOpsVerbToPoints(fVerb));
        return fPts[n];
    }

    void dump() const;

    void set(const SkDQuad &quad)
    {
        for (int index = 0; index < SkDQuad::kPointCount; ++index) {
            fPts[index] = quad[index].asSkPoint();
        }
        SkDEBUGCODE(fWeight = 1);
        SkDEBUGCODE(fVerb = SkPath::kQuad_Verb);
    }

    void set(const SkDCubic &cubic)
    {
        for (int index = 0; index < SkDCubic::kPointCount; ++index) {
            fPts[index] = cubic[index].asSkPoint();
        }
        SkDEBUGCODE(fWeight = 1);
        SkDEBUGCODE(fVerb = SkPath::kCubic_Verb);
    }
};

struct SkDCurve {
    union {
        SkDLine fLine;
        SkDQuad fQuad;
        SkDConic fConic;
        SkDCubic fCubic;
    };
    SkDEBUGCODE(SkPath::Verb fVerb);

    const SkDPoint &operator[](int n) const
    {
        SkASSERT(n >= 0 && n <= SkPathOpsVerbToPoints(fVerb));
        return fCubic[n];
    }

    SkDPoint &operator[](int n)
    {
        SkASSERT(n >= 0 && n <= SkPathOpsVerbToPoints(fVerb));
        return fCubic[n];
    }

    SkDPoint conicTop(const SkPoint curve[3], SkScalar curveWeight, double s, double e, double *topT);
    SkDPoint cubicTop(const SkPoint curve[4], SkScalar, double s, double e, double *topT);
    void dump() const;
    void dumpID(int) const;
    SkDPoint lineTop(const SkPoint[2], SkScalar, double, double, double *topT);
    double nearPoint(SkPath::Verb verb, const SkDPoint &xy, const SkDPoint &opp) const;
    SkDPoint quadTop(const SkPoint curve[3], SkScalar, double s, double e, double *topT);

    void setConicBounds(const SkPoint curve[3], SkScalar curveWeight, double s, double e, SkPathOpsBounds *);
    void setCubicBounds(const SkPoint curve[4], SkScalar, double s, double e, SkPathOpsBounds *);
    void setQuadBounds(const SkPoint curve[3], SkScalar, double s, double e, SkPathOpsBounds *);
};

class SkDCurveSweep {
public:
    bool isCurve() const
    {
        return fIsCurve;
    }
    bool isOrdered() const
    {
        return fOrdered;
    }
    void setCurveHullSweep(SkPath::Verb verb);

    SkDCurve fCurve;
    SkDVector fSweep[2];

private:
    bool fIsCurve;
    bool fOrdered; // cleared when a cubic's control point isn't between the sweep vectors
};

extern SkDPoint (SkDCurve::* const Top[])(const SkPoint curve[], SkScalar cWeight, double tStart, double tEnd,
    double *topT);

static SkDPoint dline_xy_at_t(const SkPoint a[2], SkScalar, double t)
{
    SkDLine line;
    line.set(a);
    return line.ptAtT(t);
}

static SkDPoint dquad_xy_at_t(const SkPoint a[3], SkScalar, double t)
{
    SkDQuad quad;
    quad.set(a);
    return quad.ptAtT(t);
}

static SkDPoint dconic_xy_at_t(const SkPoint a[3], SkScalar weight, double t)
{
    SkDConic conic;
    conic.set(a, weight);
    return conic.ptAtT(t);
}

static SkDPoint dcubic_xy_at_t(const SkPoint a[4], SkScalar, double t)
{
    SkDCubic cubic;
    cubic.set(a);
    return cubic.ptAtT(t);
}

static SkDPoint (* const CurveDPointAtT[])(const SkPoint[], SkScalar, double) = { nullptr, dline_xy_at_t, dquad_xy_at_t,
    dconic_xy_at_t, dcubic_xy_at_t };

static SkDPoint ddline_xy_at_t(const SkDCurve &c, double t)
{
    return c.fLine.ptAtT(t);
}

static SkDPoint ddquad_xy_at_t(const SkDCurve &c, double t)
{
    return c.fQuad.ptAtT(t);
}

static SkDPoint ddconic_xy_at_t(const SkDCurve &c, double t)
{
    return c.fConic.ptAtT(t);
}

static SkDPoint ddcubic_xy_at_t(const SkDCurve &c, double t)
{
    return c.fCubic.ptAtT(t);
}

static SkDPoint (* const CurveDDPointAtT[])(const SkDCurve &, double) = { nullptr, ddline_xy_at_t, ddquad_xy_at_t,
    ddconic_xy_at_t, ddcubic_xy_at_t };

static SkPoint fline_xy_at_t(const SkPoint a[2], SkScalar weight, double t)
{
    return dline_xy_at_t(a, weight, t).asSkPoint();
}

static SkPoint fquad_xy_at_t(const SkPoint a[3], SkScalar weight, double t)
{
    return dquad_xy_at_t(a, weight, t).asSkPoint();
}

static SkPoint fconic_xy_at_t(const SkPoint a[3], SkScalar weight, double t)
{
    return dconic_xy_at_t(a, weight, t).asSkPoint();
}

static SkPoint fcubic_xy_at_t(const SkPoint a[4], SkScalar weight, double t)
{
    return dcubic_xy_at_t(a, weight, t).asSkPoint();
}

static SkPoint (* const CurvePointAtT[])(const SkPoint[], SkScalar, double) = { nullptr, fline_xy_at_t, fquad_xy_at_t,
    fconic_xy_at_t, fcubic_xy_at_t };

static SkDVector dline_dxdy_at_t(const SkPoint a[2], SkScalar, double)
{
    SkDLine line;
    line.set(a);
    return line[1] - line[0];
}

static SkDVector dquad_dxdy_at_t(const SkPoint a[3], SkScalar, double t)
{
    SkDQuad quad;
    quad.set(a);
    return quad.dxdyAtT(t);
}

static SkDVector dconic_dxdy_at_t(const SkPoint a[3], SkScalar weight, double t)
{
    SkDConic conic;
    conic.set(a, weight);
    return conic.dxdyAtT(t);
}

static SkDVector dcubic_dxdy_at_t(const SkPoint a[4], SkScalar, double t)
{
    SkDCubic cubic;
    cubic.set(a);
    return cubic.dxdyAtT(t);
}

static SkDVector (* const CurveDSlopeAtT[])(const SkPoint[], SkScalar, double) = { nullptr, dline_dxdy_at_t,
    dquad_dxdy_at_t, dconic_dxdy_at_t, dcubic_dxdy_at_t };

static SkDVector ddline_dxdy_at_t(const SkDCurve &c, double)
{
    return c.fLine.fPts[1] - c.fLine.fPts[0];
}

static SkDVector ddquad_dxdy_at_t(const SkDCurve &c, double t)
{
    return c.fQuad.dxdyAtT(t);
}

static SkDVector ddconic_dxdy_at_t(const SkDCurve &c, double t)
{
    return c.fConic.dxdyAtT(t);
}

static SkDVector ddcubic_dxdy_at_t(const SkDCurve &c, double t)
{
    return c.fCubic.dxdyAtT(t);
}

static SkDVector (* const CurveDDSlopeAtT[])(const SkDCurve &, double) = { nullptr, ddline_dxdy_at_t, ddquad_dxdy_at_t,
    ddconic_dxdy_at_t, ddcubic_dxdy_at_t };

static SkVector fline_dxdy_at_t(const SkPoint a[2], SkScalar, double)
{
    return a[1] - a[0];
}

static SkVector fquad_dxdy_at_t(const SkPoint a[3], SkScalar weight, double t)
{
    return dquad_dxdy_at_t(a, weight, t).asSkVector();
}

static SkVector fconic_dxdy_at_t(const SkPoint a[3], SkScalar weight, double t)
{
    return dconic_dxdy_at_t(a, weight, t).asSkVector();
}

static SkVector fcubic_dxdy_at_t(const SkPoint a[4], SkScalar weight, double t)
{
    return dcubic_dxdy_at_t(a, weight, t).asSkVector();
}

static SkVector (* const CurveSlopeAtT[])(const SkPoint[], SkScalar, double) = { nullptr, fline_dxdy_at_t,
    fquad_dxdy_at_t, fconic_dxdy_at_t, fcubic_dxdy_at_t };

static bool line_is_vertical(const SkPoint a[2], SkScalar, double startT, double endT)
{
    SkDLine line;
    line.set(a);
    SkDPoint dst[2] = { line.ptAtT(startT), line.ptAtT(endT) };
    return AlmostEqualUlps(dst[0].fX, dst[1].fX);
}

static bool quad_is_vertical(const SkPoint a[3], SkScalar, double startT, double endT)
{
    SkDQuad quad;
    quad.set(a);
    SkDQuad dst = quad.subDivide(startT, endT);
    return AlmostEqualUlps(dst[0].fX, dst[1].fX) && AlmostEqualUlps(dst[1].fX, dst[2].fX);
}

static bool conic_is_vertical(const SkPoint a[3], SkScalar weight, double startT, double endT)
{
    SkDConic conic;
    conic.set(a, weight);
    SkDConic dst = conic.subDivide(startT, endT);
    return AlmostEqualUlps(dst[0].fX, dst[1].fX) && AlmostEqualUlps(dst[1].fX, dst[2].fX);
}

static bool cubic_is_vertical(const SkPoint a[4], SkScalar, double startT, double endT)
{
    SkDCubic cubic;
    cubic.set(a);
    SkDCubic dst = cubic.subDivide(startT, endT);
    return AlmostEqualUlps(dst[0].fX, dst[1].fX) && AlmostEqualUlps(dst[1].fX, dst[2].fX) &&
        AlmostEqualUlps(dst[2].fX, dst[3].fX);
}

static bool (* const CurveIsVertical[])(const SkPoint[], SkScalar, double, double) = { nullptr, line_is_vertical,
    quad_is_vertical, conic_is_vertical, cubic_is_vertical };

static void line_intersect_ray(const SkPoint a[2], SkScalar, const SkDLine &ray, SkIntersections *i)
{
    SkDLine line;
    line.set(a);
    i->intersectRay(line, ray);
}

static void quad_intersect_ray(const SkPoint a[3], SkScalar, const SkDLine &ray, SkIntersections *i)
{
    SkDQuad quad;
    quad.set(a);
    i->intersectRay(quad, ray);
}

static void conic_intersect_ray(const SkPoint a[3], SkScalar weight, const SkDLine &ray, SkIntersections *i)
{
    SkDConic conic;
    conic.set(a, weight);
    i->intersectRay(conic, ray);
}

static void cubic_intersect_ray(const SkPoint a[4], SkScalar, const SkDLine &ray, SkIntersections *i)
{
    SkDCubic cubic;
    cubic.set(a);
    i->intersectRay(cubic, ray);
}

static void (* const CurveIntersectRay[])(const SkPoint[], SkScalar, const SkDLine &, SkIntersections *) = { nullptr,
    line_intersect_ray, quad_intersect_ray, conic_intersect_ray, cubic_intersect_ray };

static void dline_intersect_ray(const SkDCurve &c, const SkDLine &ray, SkIntersections *i)
{
    i->intersectRay(c.fLine, ray);
}

static void dquad_intersect_ray(const SkDCurve &c, const SkDLine &ray, SkIntersections *i)
{
    i->intersectRay(c.fQuad, ray);
}

static void dconic_intersect_ray(const SkDCurve &c, const SkDLine &ray, SkIntersections *i)
{
    i->intersectRay(c.fConic, ray);
}

static void dcubic_intersect_ray(const SkDCurve &c, const SkDLine &ray, SkIntersections *i)
{
    i->intersectRay(c.fCubic, ray);
}

static void (* const CurveDIntersectRay[])(const SkDCurve &, const SkDLine &, SkIntersections *) = { nullptr,
    dline_intersect_ray, dquad_intersect_ray, dconic_intersect_ray, dcubic_intersect_ray };

static int line_intercept_h(const SkPoint a[2], SkScalar, SkScalar y, double *roots)
{
    if (a[0].fY == a[1].fY) {
        return false;
    }
    SkDLine line;
    roots[0] = SkIntersections::HorizontalIntercept(line.set(a), y);
    return between(0, roots[0], 1);
}

static int line_intercept_v(const SkPoint a[2], SkScalar, SkScalar x, double *roots)
{
    if (a[0].fX == a[1].fX) {
        return false;
    }
    SkDLine line;
    roots[0] = SkIntersections::VerticalIntercept(line.set(a), x);
    return between(0, roots[0], 1);
}

static int quad_intercept_h(const SkPoint a[2], SkScalar, SkScalar y, double *roots)
{
    SkDQuad quad;
    return SkIntersections::HorizontalIntercept(quad.set(a), y, roots);
}

static int quad_intercept_v(const SkPoint a[2], SkScalar, SkScalar x, double *roots)
{
    SkDQuad quad;
    return SkIntersections::VerticalIntercept(quad.set(a), x, roots);
}

static int conic_intercept_h(const SkPoint a[2], SkScalar w, SkScalar y, double *roots)
{
    SkDConic conic;
    return SkIntersections::HorizontalIntercept(conic.set(a, w), y, roots);
}

static int conic_intercept_v(const SkPoint a[2], SkScalar w, SkScalar x, double *roots)
{
    SkDConic conic;
    return SkIntersections::VerticalIntercept(conic.set(a, w), x, roots);
}

static int cubic_intercept_h(const SkPoint a[3], SkScalar, SkScalar y, double *roots)
{
    SkDCubic cubic;
    return cubic.set(a).horizontalIntersect(y, roots);
}

static int cubic_intercept_v(const SkPoint a[3], SkScalar, SkScalar x, double *roots)
{
    SkDCubic cubic;
    return cubic.set(a).verticalIntersect(x, roots);
}

static int (* const CurveIntercept[])(const SkPoint[], SkScalar, SkScalar, double *) = {
    nullptr,          nullptr,           line_intercept_h,  line_intercept_v,  quad_intercept_h,
    quad_intercept_v, conic_intercept_h, conic_intercept_v, cubic_intercept_h, cubic_intercept_v,
};

#endif
